Javascript Templating, AngularJS and Roo.XTemplate
Published 2012-04-09 00:00:00
Ok, as I said earlier, a nice Easter break, meant I could get back to coding for fun. The other issue I had to deal with over the weekend was how to do the templating for the Javascript application.
Having written a template engine for PHP, I can pretty much say that not using a template engine (which automatically escapes output) is essential for outputing HTML. It makes everything more maintainable, and reduces the risks that as a error prone human being, you are likely to open your application up to exploits.
Since most of the display logic on my applications is moving away from PHP generating HTML to PHP generating JSON, and the Javascript UI rendering this data and displaying it to the user. There has been one area I've slacked off on, that being sorting out a solution for using this JSON data from the PHP backend and rendering a elegant front end in HTML to show the end user.
Obviously this is not going to be indexed via google (Somehow JSON / Javascript interfaces are not very google friendly). But for applications that require authentication this not only helps scale the application under load, but also make it far easier to maintain.
My first effort to solve this issue was based on the original Template code in Roo (Roo.Template) however initially I decided to do the implementation in PHP. The concept was to take a group of 'HTML' template files, and use the PHP to convert those files into 'Javascript functions'. so calling the generated javascript function with 'data' as the argument, it would return the HTML to be rendered.
This basic implementation supported things like conditional inclusion, embedding Javascript, nested functions, looping and much more. It was however horribly crude and rather fragile if the template syntax was invalid. I used this for a few files relating to the ticket rendering in web.mtrack, however, I was never really happy with the solution.
Read on for my opinion / review of Anjular and the introduction to the updated Roo.XTemplate
AngularJS
Some of the guys in the office I work with have been using AngularJS, and Bo Fussing gave a short introduction at codaholics HK, (just before my RooJS introduction). This definatly perked my interest, but I've not really had time to look at in detail to see if it could resolve or improve the solution for this rendering issue.
For those that have not heard of AngularJS, it's basic premise is to convert HTML into a MVC style application, where the HTML is the view, (I guess AngularJS is controlling it all), and you provide a Model (or data), that is to be rendered.
One thing I really liked about AngularJS was the way attributes of HTML are used for things like looping indicators etc., This is very similar to the way HTML_Template_Flexy works. It enable the 'template' to be rendered in a regular HTML editor, and to get a rough preview of the expected output without having to run it through the tool.
You can wander over to the AngularJS website to get a better idea of what it's all about, however even when I first saw it, I did start to get concerned about some of the design decisions. Then after reviewing the code I decided to give it a miss, for the following reasons.
Code flow.
One of the core premises of Angular is the dynamic updating of rendered layout by just modifying data structures. For example a property in a template {{somevalue}} is linked to someobject.somevalue. if you modify the value of that variable/property, it will update the view (HTML). From my understanding this is done by polling the object, and checking for changes.
This smells like trouble waiting to happen, The normal way to trigger a change in a view would be to call a method, which would then mess arround resolving stuff, and make some changes to the DOM. if anything goes wrong, you could run the debugger and step through the program and see why it was not working. In Angular's logic, you would make a change in one code flow, and you would have to be tracking the update monitoring in the library to understand why events did not occur as expected. I've been bitten enough by this kind of 'threaded' or even fake 'threaded' style application to know it's frequently more trouble than it's worth.
I also have suspicions that monitoring a large data object for changing may have serious performance issues in larger applications. It might work well for your little JQuery quick sexy web effects, but I doubt it will work with large applications.
[UPDATE] As Igor responded the render effect trigger is not polled, but done by wrapping event handlers and redrawing after the event handler returns.
This is definitively an improvement on polling but does involve a little 'magic' that may be done better by forcing the developer to refresh() manually
Readable Source
Before using any kind of library, I pretty much always look at the source code. The reality is that no code is perfect, and when the sh*t hit's the fan, you need to know, can I really dig into this code, within a reasonable timeframe, fix it and deliver a solution without to many issues.
The code for Angular is well documented, lot's of docblock style comments, however that is about where it ends, understanding the mapping of code, classes to files is a complete mess. In RooJS if you need to find the source to Roo.form.ComboBox, look in roojs1/Roo/form/ComboBox.js .. it's that simple.
Angular uses a bundle builder discussed below, what results is code that you read having very little relation to what is actually supplied to the browser. functions and properties defined in the source appear to be public, however are actually made private by the builder), My guess is that you are unlikely to be able to introspect values using the webkit debugger.
[UPDATE] Igor has clarified that the code basically wraps the files in an anonymous function when building.
While this is one of the two approaches available to handle namespace pollution, I personally think this is hugely troublesome in the long run, using a namespace tree has proven to be faster and easier to develop and debug, and is something I would always recommend over this method.
There is also the sense that the developers may have spent too much time looking at JQuery, code like this " attr.$$observers[name] = " is not a sign of quality code. JQuery's primary design was done when bandwidth was limited, and everyone wanted to add a sexy effect to their web page. It was never originally designed for large applications, scaling to solve that problem space just gets messy very quickly.
Building the bundle..
While there is a new extension to Firefox/Chrome comming up that allows you to map compiled javascript code back to the original source, the hastle in setting this up and ensuring it works correctly adds yet another point of failure. AngularJS's 'make' file does quite a considerable amount of munging of the source code. This is the complete opposite philosophy to the RooJS build technique.
In RooJS, the basic principle is that 'debug' build is just all the source code added together in a specific order. The 'compressed' version is pretty similar except it strips whitespace, does variable shortening replacement etc.. But it's simple , KISS...
Stable codebase
One of the biggest complaints about Angular I've heard is the rather loose attitude to BC that they have, although it's claimed to be in RC stage, it appears that breaking any code currently being developed to use it is a common situation, and breaking it in fundimental ways is also occuring.
RooJS, is old and stale in comparison, but breaking existing code is not an option, new features are always added in a way that old stuff is not broken, and if the new way is 'prefered', calling Roo.log() (a portable wrapper to console.log) with a message is about as far as reporting issues go.
Roo.XTemplate
Anyway enough said, Angular while is quite impressive (especially if you play with the examples), The original problem I was trying to solve was rendering the commits for a git repo on a single day by a single user. The backend system can easily return the list of changes, the idea was to play around rendering this as HTML in a panel. (rather than using a classic RooJS grids)
The original javascript code in RooJS is based on ExtJS 1.1, which had a nice LGPL licence and assets from pre-1.0 which had BSD licences. Some of the code that has been ported (eg. Ext changed to Roo) has just been left in the repository without much attention or love.
One of these files was Roo/XTemplate.js, It was originally not documented, and no examples where available to understand what it did. It extends the basic Roo Template class Roo.Template, which does quite trivial replacement of {somevar} in a HTML Snippet and overlays properties of data provided. It does this by creating a javascript function on the fly and calls it to render.
This base class allows you to call methods on the Roo.util.Format class to format the data {somevar:usMoney()}, or methods on the Template using {somevar:this.callsomething()}.
There is another class Roo.MasterTemplate which has support for named templates, however neither support conditional rendering or looping.
So after looking at the Roo.XTemplate code, it became clear that it may do some of these things. The original code in Roo.XTemplate supports these feature using the fake HTML tag <tpl> and depending on what property it will add code to the template to conditionally use this data.
<tpl for="a_variable or condition..">CONTENT</tpl>
<tpl if="a_variable or condition">CONTENT</tpl>
<tpl exec="some javascript">CONTENT</tpl>
There was a number of issues with the existing implementation of XTemplate,
* the original code did not handle universal constructor. (It now does)
- * error handling was pretty much non-existent, if you made an error in the template it would just crash the rendering process...
- It now reports all missing properties using the console.
- * variable where not escaped by default (this is still a flaw with the original Roo.Template code),
- The :raw format method is now used to explicity output raw values, otherwise it is html escaped.
- * It did not support property method calls like format('Y-m-d') on date objects.
- this is hacked in using somevar.format:('Y-m-d');
- * the method for determining undefined properties was flaky at best, value[xxx] === undefined. worked fine on simple tests, however nested properties an methods just broke badly. it now uses 'with(values)' and tests all elements of a nested property (eg. is undefined a, a.b or a.b.c)
- * no support for remote templates. All of the template classes now support {url : 'http://....'} as a constructor argument, if that occurs, it will fetch the template from a remote url, making it easier to maintain.
- * it's regex based, unfortunatly it still is, but there is at least a good base to build a DOM based parser.
So this is the new look of the template usage..
The template
<tpl for="parray">
<li class="change-log-day"><aname="changedate-{changedate.format:('Y-m-d')}"
>{changedate.format:('d / M / Y')}</a>
<tpl if="typeof(first_of_day) != 'undefined' && first_of_day * 1">
FIRST
</tpl>
</li>
</tpl>
The consumer
Roo.onReady(function() {
var tpl = new Roo.XTemplate({
url : baseURL + 'xtemplate-test.html',
});
tpl.append(Roo.get('body'),{
parray : [
{
changedate : new Date()
},
{
changedate : new Date()
}
]
});
});
Follow us
-
- Some thoughts on the language server and its usefulness in the roobuilder
- Roo Builder for Gtk4 moving forward
- Clustered Web Applications - Mysql and File replication
- GitLive - Branching - Merging
- PDO_DataObject Released
- PDO_DataObject is under way
- Mass email Marketing and anti-spam - some of the how-to..
- Hydra - Recruitment done right
Blog Latest
-
Twitter - @Roojs